
package com.orhandemirel.torrent;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.security.MessageDigest;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Selcuk Orhan DEMIREL <orhandemirel@msn.com>
 */

public class TorrentFile {

    private Map dictionary;
    private Map infoDictionary;
    private Set keys;
    private int length;
    private Info info;
    
    private String[] trackers;
    private Date creationDate;
    private String comment;
    private String createdBy;
    private String encoding;

    /**
     * @return the trackers
     */
    public String[] getTrackers() {
        return trackers;
    }

    /**
     * @return the creationDate
     */
    public Date getCreationDate() {
        return creationDate;
    }

    /**
     * @return the comment
     */
    public String getComment() {
        return comment;
    }

    /**
     * @return the createdBy
     */
    public String getCreatedBy() {
        return createdBy;
    }

    /**
     * @return the info
     */
    public Info getInfo() {
        return info;
    }



    public static class Info
    {

        /**
         * @return the rootDir
         */
        public String getRootDir() {
            return rootDir;
        }

        /**
         * @return the pieceLength
         */
        public int getPieceLength() {
            return pieceLength;
        }

        /**
         * @return the pieces
         */
        public byte[][] getPieces() {
            return pieces;
        }

        /**
         * @return the files
         */
        public File[] getFiles() {
            return files;
        }

        /**
         * @param rootDir the rootDir to set
         */
        private void setRootDir(String rootDir) {
            this.rootDir = rootDir;
        }

        /**
         * @param pieceLength the pieceLength to set
         */
        private void setPieceLength(int pieceLength) {
            this.pieceLength = pieceLength;
        }

        /**
         * @param pieces the pieces to set
         */
        private void setPieces(byte[][] pieces) {
            this.pieces = pieces;
        }

        /**
         * @param files the files to set
         */
        private void setFiles(File[] files) {
            this.files = files;
        }

        public static class File {
        private int length;
        private String name;
        private String path;
        private byte[] md5;

            /**
             * @return the length
             */
            public int getLength() {
                return length;
            }

            /**
             * @return the name
             */
            public String getName() {
                return name;
            }

            /**
             * @return the path
             */
            public String getPath() {
                return path;
            }

            /**
             * @return the md5
             */
            public byte[] getMd5() {
                return md5;
            }

            /**
             * @param length the length to set
             */
            private void setLength(int length) {
                this.length = length;
            }

            /**
             * @param name the name to set
             */
            private void setName(String name) {
                this.name = name;
            }

            /**
             * @param path the path to set
             */
            private void setPath(String path) {
                this.path = path;
            }

            /**
             * @param md5 the md5 to set
             */
            private void setMd5(byte[] md5) {
                this.md5 = md5;
            }
        }

        private String rootDir;
        private int pieceLength;
        private byte[][] pieces;
        private File[] files;
    }

    public TorrentFile(File file) throws FileNotFoundException, IOException
    {
        Load(file);
    }
    public TorrentFile(String fileName) throws FileNotFoundException, IOException
    {
        File tfile = new File(fileName);
        Load(tfile);
    }

    private void Load(File tfile) throws FileNotFoundException, IOException
    {
        FileInputStream fs;
        fs = new FileInputStream(tfile);
        length= (int)tfile.length();
        byte[] cbuf = new byte[ length ];
        fs.read(cbuf);
        fs.close();


        Object o = Bencoding.Decode(cbuf);
        dictionary = (Map)o;
        infoDictionary = (Map) dictionary.get("info");
        keys = dictionary.keySet();

        // encoding of text strings
        if( dictionary.containsKey("encoding"))
        {
            encoding = new String( (byte[]) dictionary.get("encoding"), "ASCII");
        }
        else
        {
            encoding = "UTF-8";
        }

        // Create info object
        info = new Info();

        info.pieceLength = (Integer) infoDictionary.get("piece length");

        byte[] sha1 = (byte[]) infoDictionary.get("pieces");
        int piecesNum = sha1.length / 20;
        info.pieces = new byte[piecesNum][];

        for(int i=0; i<piecesNum; i++)
        {
            info.pieces[i] = new byte[20];
             info.pieces[i] =  Arrays.copyOfRange( sha1, i*20, (i*20)+20 );
        }

        if(isMultiFile())   // multi-file torrent
        {
            info.rootDir = new String( (byte[]) infoDictionary.get("name"), encoding);
            ArrayList fileList = (ArrayList) infoDictionary.get("files");
            int fileNum = fileList.size();
            info.files = new Info.File[ fileNum ];

            for(int i=0; i< fileNum; i++)
            {
                info.files[i] = new Info.File();

                Map fileMap = (Map) fileList.get(i);
                info.files[i].length = (Integer) fileMap.get("length");

                ArrayList pathList = (ArrayList) fileMap.get("path");

                StringBuilder pathAll = new StringBuilder("");

                for(int j = 0; j < pathList.size() - 1 ; j++)
                {
                    pathAll.append( new String ( (byte[]) pathList.get(j), encoding ) );
                }
                info.files[i].path = pathAll.toString();

                info.files[i].name = new String( (byte[]) pathList.get( pathList.size() - 1 ), encoding);

                if(fileMap.containsKey("md5sum"))
                {
                    byte[] md5hash = (byte[]) fileMap.get("md5sum");
                    info.files[i].md5 = Arrays.copyOf( md5hash, md5hash.length );
                }
                else
                {
                    info.files[i].md5 = null;
                }
            }

        }
        else    // single-file torrent
        {
            info.rootDir = "";
            info.files = new Info.File[1];
            info.files[0] = new Info.File();
            info.files[0].length = (Integer) infoDictionary.get("length");
            info.files[0].name = new String( (byte[]) infoDictionary.get("name"), encoding);
            info.files[0].path = "";
            if(dictionary.containsKey("md5sum"))
            {
                byte[] md5hash = (byte[]) dictionary.get("md5sum");
                info.files[0].md5 = Arrays.copyOf( md5hash, md5hash.length );
            }
            else
            {
                info.files[0].md5 = null;
            }
        }


        ArrayList announceList = new ArrayList();
        
        if(dictionary.containsKey("announce-list"))
        {
            ArrayList trackerLists = (ArrayList) dictionary.get("announce-list");
            for(Object trackerList : trackerLists)
            {
                ArrayList list = (ArrayList) trackerList;
                for(Object tracker : list)
                {
                    announceList.add( new String((byte[]) tracker, encoding) );
                }
            }
        }
        else
        {
            announceList.add(  new String ((byte[]) dictionary.get("announce"), encoding)  );
        }

        trackers = Arrays.copyOf(announceList.toArray(), announceList.size(), String[].class);



        if(dictionary.containsKey("creation date"))
        {
            creationDate = new Date( ((Number)dictionary.get("creation date")).longValue() * 1000);
        }
        else
        {
            creationDate = new Date(0);
        }

         if(dictionary.containsKey("comment"))
        {
            comment = new String( (byte[]) dictionary.get("comment"), encoding);
        }
         else
         {
            comment = "";
         }

         if(dictionary.containsKey("created by"))
        {
            createdBy = new String( (byte[]) dictionary.get("created by"), encoding);
        }
         else
         {
            createdBy = "";
         }

    }

    /**
     * @return the main dictionary
     */
    public Map getDictionary() {
        return dictionary;
    }

    /**
     * @return the keys of the dictionary
     */
    public Set getKeys() {
        return keys;
    }


    public final boolean isMultiFile()
    {
        return ((Map)dictionary.get("info")).containsKey("files");
    }

     public final String getInfoHash()
    {
        MessageDigest mDigest;
        try {
            mDigest = MessageDigest.getInstance("SHA1");
            byte[] bencodedInfo = Bencoding.Encode(infoDictionary, encoding);

            mDigest.update(bencodedInfo);
            byte[] infoHash = mDigest.digest();
            
            StringBuilder hex = new StringBuilder();
            for(int i=0;i< infoHash.length;i++)
            {
                String hexstr = Integer.toHexString( ((int)infoHash[i] & 0xff  ));
                if( hexstr.length() == 1 )
                {
                    hexstr = "0" + hexstr;
                }
                hex.append( hexstr );
            }
            
            return hex.toString().toUpperCase();

        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(TorrentFile.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    public final String getURLEncodedInfoHash()
    {
        String hex = getInfoHash();
        StringBuilder urlEncoded = new StringBuilder();
        for(int i=0;i< hex.length(); i+=2)
        {
            urlEncoded.append("%");
            urlEncoded.append(hex.charAt(i));
            urlEncoded.append(hex.charAt(i+1));
        }

        return urlEncoded.toString();
    }
}